﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Windows;
using System.Windows.Resources;
using System.Xml.Linq;

namespace Moonwish.Silverlight.OsAvatar.Utility
{
    /// <summary>
    /// 功能：Xap包程序集管理
    /// 实现：直接从Xap中加载程序集
    /// write by zhlin 2012-03-19。
    /// </summary>
    public class XapAssembliesManager
    {
        public event EventHandler<LoadAssemblyFromXapEventArgs> LoadAssembliesFromXapCompleted;

        public void LoadAssembliesFromXap(string xapFileName)
        {
            using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
            {
                var needDown = true;
                if (storage.FileExists(xapFileName))
                {
                    var localVersion = storage.GetLastWriteTime(xapFileName);
                    var serverVersion = DateTime.Parse(GetXapVersionMark(xapFileName));
                    needDown = localVersion < serverVersion;
                }
                if (needDown)
                {
                    var client = new WebClient();
                    client.OpenReadCompleted += (s, e) =>
                    {
                        if (e.Error != null)
                        {
                            InvokeLoadAssembliesFromXapCompleted(new LoadAssemblyFromXapEventArgs{Error = e.Error});
                            return;
                        }
                        SaveXapToLocal(xapFileName, e.Result);

                        ReadXapFromLocal(xapFileName);

                    };
                    client.OpenReadAsync(new Uri(xapFileName, UriKind.RelativeOrAbsolute));
                }
                else
                {
                    ReadXapFromLocal(xapFileName);
                }
            }
        }

        void InvokeLoadAssembliesFromXapCompleted(LoadAssemblyFromXapEventArgs e)
        {
            var handler = LoadAssembliesFromXapCompleted;

            if (handler != null)
            {
                handler(this, e);
            }
        }

        private void ReadXapFromLocal(string xapFileName)
        {
            var assemblies = new List<Assembly>();
            using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
            {
                try
                {
                    var fileStream = storage.OpenFile(xapFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                    var manifestResourceStream = Application.GetResourceStream(new StreamResourceInfo(fileStream, null),
                                                                               new Uri("AppManifest.xaml",
                                                                                       UriKind.RelativeOrAbsolute));
                    var root = XDocument.Parse(new StreamReader(manifestResourceStream.Stream).ReadToEnd()).Root;
                    if (root != null)
                    {
                        var parts = root.Elements().First(obj => obj.Name.LocalName == "Deployment.Parts").Elements();
                        assemblies.AddRange(from element in parts
                                            select element.Attribute("Source")
                                            into attribute
                                            where attribute != null
                                            select attribute.Value
                                            into uriString
                                            let part = new AssemblyPart()
                                            let assemblyResourceStream =
                                                Application.GetResourceStream(
                                                    new StreamResourceInfo(fileStream, "application/binary"),
                                                    new Uri(uriString, UriKind.RelativeOrAbsolute))
                                            select part.Load(assemblyResourceStream.Stream));

                        var extParts =
                            root.Elements().FirstOrDefault(obj => obj.Name.LocalName == "Deployment.ExternalParts");
                        if (extParts != null && extParts.Elements().Any())
                        {
                            var requestExtZips = extParts.Elements().Select(obj =>
                                                                                {
                                                                                    var xAttribute =
                                                                                        obj.Attribute("Source");
                                                                                    return xAttribute != null
                                                                                               ? xAttribute.Value
                                                                                               : null;
                                                                                }).ToList();
                            var deployExtZips =
                                Deployment.Current.ExternalParts.Select(obj => obj.Source.OriginalString).ToList();
                            var missExtZips =
                                (from r in requestExtZips where !deployExtZips.Contains(r) select r).ToList();
                            var downZips = missExtZips.Where(
                                missExtZip =>
                                Deployment.Current.Parts.FirstOrDefault(obj => obj.Source == missExtZip) == null).ToList();
                            var prepare = 0;
                            foreach (var missExtZip in downZips)
                            {
                                if (!storage.FileExists(missExtZip))
                                {
                                    var client = new WebClient();
                                    
                                    client.OpenReadCompleted += (s, e) =>
                                                                    {
                                                                        if (e.Error != null)
                                                                        {
                                                                            InvokeLoadAssembliesFromXapCompleted(
                                                                                new LoadAssemblyFromXapEventArgs { Error = e.Error });
                                                                            return;
                                                                        }
                                                                        var target = e.UserState.ToString();

                                                                        SaveXapToLocal(target, e.Result);

                                                                        LoadExtPartFromLocal(target);

                                                                        prepare++;

                                                                        if (prepare == downZips.Count)
                                                                        {
                                                                            InvokeLoadAssembliesFromXapCompleted(
                                                                                new LoadAssemblyFromXapEventArgs
                                                                                    {Assemblies = assemblies});
                                                                        }
                                                                    };
                                    client.OpenReadAsync(new Uri(missExtZip, UriKind.RelativeOrAbsolute), missExtZip);
                                }
                                else
                                {
                                    LoadExtPartFromLocal(missExtZip);

                                    prepare++;
                                }
                            }
                            if (prepare == downZips.Count)
                            {
                                InvokeLoadAssembliesFromXapCompleted(new LoadAssemblyFromXapEventArgs { Assemblies = assemblies });
                            }
                        }
                        else
                        {
                            InvokeLoadAssembliesFromXapCompleted(new LoadAssemblyFromXapEventArgs { Assemblies = assemblies });
                        }
                    }
                }
                catch( Exception ex)
                {
                    InvokeLoadAssembliesFromXapCompleted(new LoadAssemblyFromXapEventArgs { Error = ex });
                }
            }
        }

        private static void SaveXapToLocal(string saveFileName, Stream stream)
        {
            if (stream == null)
            {
                return;
            }
            using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (storage.FileExists(saveFileName))
                {
                    storage.DeleteFile(saveFileName);
                }
                var fileStream = new IsolatedStorageFileStream(saveFileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite, storage);
                var content = StreamToBytes(stream);
                var writer = new BinaryWriter(fileStream, System.Text.Encoding.UTF8);
                writer.Write(content);
                writer.Close();
                fileStream.Close();
            }
        }

        private static void LoadExtPartFromLocal(string extFileName)
        {
            using (var storage = IsolatedStorageFile.GetUserStoreForApplication())
            {
                var fileStream = storage.OpenFile(extFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                var assemblyResourceStream =
                    Application.GetResourceStream(new StreamResourceInfo(fileStream, "application/binary"),
                                                  new Uri(extFileName.Replace(".zip", ".dll"), UriKind.RelativeOrAbsolute));
                var part = new AssemblyPart();
                part.Load(assemblyResourceStream.Stream);
                part.Source = extFileName;
                Deployment.Current.Parts.Add(part);
            }
        }

        private static byte[] StreamToBytes(Stream stream)
        {
            using (var ms = new MemoryStream())
            {
                var buffer = new byte[8 * 1024];
                int read;
                while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    ms.Write(buffer, 0, read);
                }

                return ms.ToArray();
            }
        }

        private static string GetXapVersionMark(string xapName)
        {
            var settings = IsolatedStorageSettings.ApplicationSettings;
            var setting = settings.FirstOrDefault(obj => obj.Key == xapName);
            return setting.Value == null ? DateTime.Now.AddHours(-1).ToString("yyyy-MM-dd HH:mm:ss") : setting.Value.ToString();
        }
    }

    /// <summary>
    /// 程序集加载完成事件EventArgs。
    /// </summary>
    public class LoadAssemblyFromXapEventArgs : EventArgs
    {
        public LoadAssemblyFromXapEventArgs()
        {
            Assemblies = new List<Assembly>();
        }

        public LoadAssemblyFromXapEventArgs(List<Assembly> assemblies)
        {
            Assemblies = assemblies;
        }

        public List<Assembly> Assemblies { get; set; }

        public Exception Error { get; set; }
    }
}
